namespace egret.socketio {

    export enum _emParser {
        CONNECT = 0,
        DISCONNECT,
        EVENT,
        ACK,
        ERROR,
        BINARY_EVENT,
        BINARY_ACK
    }

    export namespace parser {

        export interface Encoder extends Emitter {
            encode(obj: Packet, callback: Function): void;
        }

        export let Encoder: {
            new(): Encoder;
        }

        export interface Decoder extends Emitter {
            add(obj: string): void;
            destory(): void;
            onDecoded(callback: Function): void;
        }

        export let Decoder: {
            new(): Decoder;
        }

        class EncoderImpl extends Emitter implements Encoder {

            private static ERROR_PACKET = _emParser.ERROR + '"encode error"';

            encode(obj: Packet, callback: Function): void {
                if (obj.type === _emParser.BINARY_EVENT || obj.type === _emParser.BINARY_ACK) {
                    this.encodeAsBinary(obj, callback);
                } else {
                    let encoding = this.encodeAsString(obj);
                    callback([encoding]);
                }
            }

            private encodeAsString(obj: Packet): string {
                let str = '' + obj.type;

                if (_emParser.BINARY_EVENT === obj.type || _emParser.BINARY_ACK === obj.type) {
                    str += obj.attachments + '-';
                }

                if (obj.nsp && '/' !== obj.nsp) {
                    str += obj.nsp + ',';
                }

                if (obj.id) {
                    str += obj.id;
                }

                if (obj.data) {
                    let payload = this.tryStringify(obj.data);
                    if (payload !== false) {
                        str += payload;
                    } else {
                        return EncoderImpl.ERROR_PACKET;
                    }
                }

                return str;
            }

            private tryStringify(str): string | boolean {
                try {
                    return JSON.stringify(str);
                } catch (e) {
                    return false;
                }
            }

            private encodeAsBinary(obj: Packet, callback: Function) {
                //TODO
            }
        }

        class DecoderImpl extends Emitter implements Decoder {

            private reconstructor = null;

            add(obj: string): void {

                let packet: Packet;

                if (typeof obj === 'string') {
                    packet = this.decodeString(obj);
                    if (_emParser.BINARY_EVENT === packet.type || _emParser.BINARY_ACK === packet.type) {
                        this.reconstructor = new BinaryReconstructor(packet);

                    } else {
                        this.emit(_emEvent.EVENT_DECODED, packet);
                    }
                }
            }

            private decodeString(str: string): Packet {
                let i = 0;
                let p: Packet = {
                    type: Number(str.charAt(0))
                }

                if (!_emParser[p.type]) {
                    return this.error('unknown packet type ' + p.type);
                }

                if (p.type === _emParser.BINARY_EVENT || p.type === _emParser.BINARY_ACK) {
                    let buf = '';
                    while (str.charAt(++i) !== '-') {
                        buf += str.charAt(i);
                        if (i == str.length)
                            break;
                    }

                    if (buf != Number(buf).toString() || str.charAt(i) !== '-') {
                        throw new Error('Illegal attachments');
                    }

                    p.attachments = Number(buf);
                }

                if ('/' === str.charAt(i + 1)) {
                    p.nsp = '';
                    while (++i) {
                        let c = str.charAt(i);
                        if (',' === c) break;
                        p.nsp += c;
                        if (i === str.length) break;
                    }
                } else {
                    p.nsp = '/';
                }

                let next = str.charAt(i + 1);
                if ('' !== next && Number(next).toString() == next) {
                    let id = '';
                    while (++i) {
                        let c = str.charAt(i);
                        if (null == c || Number(c).toString() != c) {
                            --i;
                            break;
                        }
                        id += str.charAt(i);
                        if (i === str.length) break;
                    }
                    p.id = Number(id);
                }

                if (str.charAt(++i)) {
                    let payload = this.tryParse(str.substr(i));
                    let isPayloadValid = payload !== false && (p.type === _emParser.ERROR || isArray(payload));
                    if (isPayloadValid) {
                        p.data = payload;
                    } else {
                        return this.error('invalid payload');
                    }
                }
                return p;
            }

            private tryParse(str: string): boolean | Object {
                try {
                    return JSON.parse(str);
                } catch (e) {
                    return false;
                }
            }

            private error(msg: string): Packet {

                return {
                    type: _emParser.ERROR,
                    data: 'parser error: ' + msg
                }
            }
            destory(): void {

            }
            onDecoded(callback: Function): void {

            }
        }

        Encoder = EncoderImpl;
        Decoder = DecoderImpl;
    }


    export interface Parser {
        Encoder: parser.Encoder;
        Decoder: parser.Decoder;
    }

}